#if 0 /* core test interface */
/* Data source and destination managers: memory buffers. */
EXTERN(void)jpeg_mem_dest(j_compress_ptr cinfo, unsigned char ** outbuffer,
        unsigned long * outsize);
EXTERN(void)jpeg_mem_src(j_decompress_ptr cinfo, unsigned char * inbuffer,
        unsigned long insize);

typedef JSAMPLE *JSAMPROW;      /* ptr to one image row of pixel samples. */
typedef JSAMPROW *JSAMPARRAY;   /* ptr to some rows (a 2-D sample array) */
typedef JSAMPARRAY *JSAMPIMAGE; /* a 3-D sample array: top index is color */

/* reading the header, before decompress */
    printf("image_width = %d\n", cinfo.image_width);
    printf("image_height = %d\n", cinfo.image_height);
    printf("num_components = %d\n", cinfo.num_components);
/* after decompress */
    printf("output_width = %d\n", cinfo.output_width);
    printf("output_height = %d\n", cinfo.output_height);
    printf("output_components = %d\n", cinfo.output_components);
#endif

#include<csetjmp>
#include<cstdio>
#include<jpeglib.h>
#include<cstdlib>
#include<cstring>
#include<fstream>

using namespace std;

const char *srcJpegFile = "./src.jpg";
const char *dstJpegFile = "./new.jpg";

/* first read in buffer */
static int g_nImageWidth;
static int g_nImageHeight;
static unsigned char * g_ImageBuffer;
static long unsigned int g_nImageSize;

static unsigned char * g_ImageBufferCprs; /* compress */
static long unsigned int g_nImageSizeCprs;
static unsigned char * g_ImageBufferNew;

#if 1 /* recommand handler interface */
struct my_error_mgr {
    struct jpeg_error_mgr pub;    /* "public" fields */

    jmp_buf setjmp_buffer;        /* for return to caller */
};
typedef struct my_error_mgr * my_error_ptr;
METHODDEF(void) my_error_exit (j_common_ptr cinfo)
{
    /* cinfo->err really points to a my_error_mgr struct, so coerce pointer */
    my_error_ptr myerr = (my_error_ptr) cinfo->err;

    /* Always display the message. */
    /* We could postpone this until after returning, if we chose. */
    (*cinfo->err->output_message) (cinfo);

    /* Return control to the setjmp point */
    longjmp(myerr->setjmp_buffer, 1);
}
#endif

GLOBAL(int) read_JPEG_file2mem(unsigned char **dstMem,
        const char * filename) {
    /* read in a jpeg file */
    struct jpeg_decompress_struct cinfo; /* decompress object */
    struct my_error_mgr jerr;
    FILE * infile;          /* source file */
    int row_stride;         /* physical row width in output buffer */

    if ((infile = fopen(filename, "rb")) == NULL) {
        fprintf(stderr, "can't open %s\n", filename);
        return 0;
    }

    cinfo.err = jpeg_std_error(&jerr.pub);
    jerr.pub.error_exit = my_error_exit;
    if (setjmp(jerr.setjmp_buffer)) {
        jpeg_destroy_decompress(&cinfo);
        fclose(infile);
        return 0;
    }

    jpeg_create_decompress(&cinfo);

    jpeg_stdio_src(&cinfo, infile);
    jpeg_read_header(&cinfo, TRUE);
    jpeg_start_decompress(&cinfo);

#if 1 /* output the size */
    g_nImageWidth = cinfo.output_width;
    g_nImageHeight = cinfo.output_height;
    g_nImageSize = g_nImageHeight * g_nImageWidth;
    row_stride = cinfo.output_width * cinfo.output_components;
#endif

    *dstMem = (unsigned char *)
        malloc(cinfo.output_width * cinfo.output_height *
                cinfo.output_components);

    JSAMPROW buffer = (JSAMPLE *)malloc(sizeof(JSAMPLE) * row_stride);

    int counter = 0;

    while (cinfo.output_scanline < cinfo.output_height) {
        jpeg_read_scanlines(&cinfo, &buffer, 1);
        memcpy(*dstMem + counter, buffer, row_stride);
        counter += row_stride;
    }

    (void)jpeg_finish_decompress(&cinfo);
    (void)jpeg_destroy_decompress(&cinfo);
    fclose(infile);
    free(buffer);
    return 0;
}

GLOBAL(int)encompress_JPEG_m2m(unsigned char **dstMem,
        unsigned char *srcMem,
        const int quality = 70) {
    struct jpeg_compress_struct cinfo;
    struct jpeg_error_mgr jerr;
    int row_stride;     /* physical row width in image buffer */

    cinfo.err = jpeg_std_error(&jerr);
    jpeg_create_compress(&cinfo);
    jpeg_mem_dest(&cinfo, dstMem, &g_nImageSizeCprs);

    cinfo.image_width = g_nImageWidth;
    cinfo.image_height = g_nImageHeight;
    cinfo.input_components = 3;
    cinfo.in_color_space = JCS_RGB; // JCS_YCbCr or JCS_RGB;

    jpeg_set_defaults(&cinfo);
    jpeg_set_quality(&cinfo, quality, TRUE);
    jpeg_start_compress(&cinfo, TRUE);

    row_stride = g_nImageWidth * 3; //cinfo.input_components;
    JSAMPROW row_pointer;
    while (cinfo.next_scanline < cinfo.image_height) {
        int line = cinfo.next_scanline;
        row_pointer = srcMem + (line * row_stride);
        jpeg_write_scanlines(&cinfo, &row_pointer, 1);
    }
    (void)jpeg_finish_compress(&cinfo);
    (void)jpeg_destroy_compress(&cinfo);
    return 0;
}

GLOBAL(int)decompress_JPEG_m2m(unsigned char **dstMem,
        unsigned char *srcMem) {
    struct jpeg_decompress_struct cinfo; /* decompress object */
    struct my_error_mgr jerr;
    int row_stride;         /* physical row width in output buffer */

    cinfo.err = jpeg_std_error(&jerr.pub);
    jerr.pub.error_exit = my_error_exit;
    if (setjmp(jerr.setjmp_buffer)) {
        jpeg_destroy_decompress(&cinfo);
        return 0;
    }

    jpeg_create_decompress(&cinfo);

    jpeg_mem_src(&cinfo, srcMem, g_nImageSizeCprs);
    jpeg_read_header(&cinfo, TRUE);
    jpeg_start_decompress(&cinfo);

    row_stride = cinfo.output_width * cinfo.output_components;


    //JSAMPARRAY buffer = (JSAMPARRAY)malloc(sizeof(JSAMPROW));
    //buffer[0] = (JSAMPROW)malloc(sizeof(JSAMPLE) * row_stride);
    g_ImageBufferNew = (unsigned char *)
        malloc(cinfo.output_width * cinfo.output_height *
                cinfo.output_components);

    JSAMPROW buffer = (JSAMPLE *)malloc(sizeof(JSAMPLE) * row_stride);
    int counter = 0;
    while (cinfo.output_scanline < cinfo.output_height) {
        jpeg_read_scanlines(&cinfo, &buffer, 1);
        memcpy(*dstMem + counter, buffer, row_stride);
        counter += row_stride;
    }

    (void)jpeg_finish_decompress(&cinfo);
    (void)jpeg_destroy_decompress(&cinfo);
    free(buffer);
    return 0;
}

GLOBAL(int)write_JPEG_mem2file(const char *filename,
        unsigned char *srcMem,
        const int quality = 70) {
    struct jpeg_compress_struct cinfo;
    struct jpeg_error_mgr jerr;
    FILE * outfile;     /* target file */
    int row_stride;     /* physical row width in image buffer */

    cinfo.err = jpeg_std_error(&jerr);
    jpeg_create_compress(&cinfo);

    if ((outfile = fopen(filename, "wb")) == NULL) {
        fprintf(stderr, "can't open %s\n", filename);
        exit(1);
    }
    jpeg_stdio_dest(&cinfo, outfile);

    cinfo.image_width = g_nImageWidth;
    cinfo.image_height = g_nImageHeight;
    cinfo.input_components = 3;
    cinfo.in_color_space = JCS_RGB; // JCS_YCbCr or JCS_RGB;
    jpeg_set_defaults(&cinfo);
    jpeg_set_quality(&cinfo, quality, TRUE);
    jpeg_start_compress(&cinfo, TRUE);

    row_stride = g_nImageWidth * 3; //cinfo.input_components;
    JSAMPROW row_pointer;
    while (cinfo.next_scanline < cinfo.image_height) {
        int line = cinfo.next_scanline;
        row_pointer = srcMem + (line * row_stride);
        jpeg_write_scanlines(&cinfo, &row_pointer, 1);
    }

    (void)jpeg_finish_compress(&cinfo);
    (void)jpeg_destroy_compress(&cinfo);
    fclose(outfile);
    return 0;
}

int main(int argc, char **argv) {
    /* [1] read jpg file, decompress to mem-g_ImageBuffer */
    read_JPEG_file2mem(&g_ImageBuffer, srcJpegFile);
    printf("raw size is %ld.\n", g_nImageSize);

    /* [2] encompress jpg from mem-g_ImageBuffer to mem-g_ImageBufferCprs */
    encompress_JPEG_m2m(&g_ImageBufferCprs, g_ImageBuffer);
    printf("compressed size is %ld.\n", g_nImageSizeCprs);

    // direclty write compress mem-buffer-data to file
    std::ofstream of("inMemJpeg.jpg");
    of.write((const char *)g_ImageBufferCprs, g_nImageSizeCprs);
	of.close();

    /* [3] decompress jpg from mem-g_ImageBufferCprs to mem-g_ImageBufferNew */
    decompress_JPEG_m2m(&g_ImageBufferNew, g_ImageBufferCprs);

    /* [4] write jpg file, encompress mem-g_ImageBufferNew to file */
    write_JPEG_mem2file(dstJpegFile, g_ImageBufferNew);

    return 0;
}
